package com.github.cakin.shiro.chapter23.session.scheduler;

import com.github.cakin.shiro.chapter23.utils.SerializableUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionValidationScheduler;
import org.apache.shiro.session.mgt.ValidatingSessionManager;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.AbstractValidatingSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * @className: MySessionValidationScheduler
 * @description: 自定义会话验证调度器，改造自 ExecutorServiceSessionValidationScheduler
 * @date: 2020/5/22
 * @author: cakin
 */
public class MySqlSessionValidationScheduler implements SessionValidationScheduler, Runnable {
    /**
     * 定义一个 jdbcTemplate
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * Private internal log instance.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(MySqlSessionValidationScheduler.class);
    /**
     * 定义 sessionManager
     */
    private ValidatingSessionManager sessionManager;
    /**
     * 定义 service
     */
    private ScheduledExecutorService service;
    /**
     * interval
     */
    private long interval = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
    /**
     * enabled
     */
    private boolean enabled = false;
    /**
     * NUM20
     */
    private static final int NUM20 = 20;

    public MySqlSessionValidationScheduler() {
        super();
    }

    public ValidatingSessionManager getSessionManager() {
        return sessionManager;
    }

    public void setSessionManager( ValidatingSessionManager sessionManager ) {
        this.sessionManager = sessionManager;
    }

    public long getInterval() {
        return interval;
    }

    public void setInterval( long interval ) {
        this.interval = interval;
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    /**
     * Creates a single thread {@link java.util.concurrent.ScheduledExecutorService} to validate sessions at fixed intervals
     * and enables this scheduler. The executor is created as a daemon thread to allow JVM to shut down
     */
    //TODO Implement an integration test to test for jvm exit as part of the standalone example
    // (so we don't have to change the unit test execution model for the core module)
    public void enableSessionValidation() {
        if (this.interval > 0L) {
            this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
                public Thread newThread( Runnable r ) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    return thread;
                }
            });
            this.service.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS);
            this.enabled = true;
        }
    }

    public void run() {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Executing session validation...");
        }
        long startTime = System.currentTimeMillis();

        // 分页获取会话并验证
        String sql = "select session from sessions limit ?,?";
        int start = 0; // 起始记录
        int size = NUM20; // 每页大小
        List<String> sessionList = jdbcTemplate.queryForList(sql, String.class, start, size);
        while (sessionList.size() > 0) {
            for (String sessionStr : sessionList) {
                try {
                    Session session = SerializableUtils.deserialize(sessionStr);
                    Method validateMethod = ReflectionUtils.findMethod(AbstractValidatingSessionManager.class, "validate",
                            Session.class, SessionKey.class);
                    validateMethod.setAccessible(true);
                    ReflectionUtils.invokeMethod(validateMethod, sessionManager, session, new DefaultSessionKey(session.getId()));
                } catch (Exception e) {
                    //ignore
                }
            }
            start = start + size;
            sessionList = jdbcTemplate.queryForList(sql, String.class, start, size);
        }

        long stopTime = System.currentTimeMillis();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
        }
    }

    public void disableSessionValidation() {
        this.service.shutdownNow();
        this.enabled = false;
    }
}
